import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;

/**
 * MD5计算工具 xuxile 2017-09-13
 * txt 2021-3-29 实现了MD5 16进制转字符串
 */
public class Md5CaculateUtil {

    /**
     * 获取一个文件的md5值(可处理大文件)
     *
     * @return md5 value
     */
    public static String getMD5(File file) {
        FileInputStream fileInputStream = null;
        try {
            MessageDigest MD5 = MessageDigest.getInstance("MD5");
            fileInputStream = new FileInputStream(file);
            byte[] buffer = new byte[8192];
            int length;
            while ((length = fileInputStream.read(buffer)) != -1) {
                MD5.update(buffer, 0, length);
            }
            return MD5ArrFromHexToString(MD5.digest());
            //return new String(MD5.digest());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    static String MD5ArrFromHexToString(byte[] array) {
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < array.length; i++) {
            //MD5值有128bit,即128个0和1
            //输入为MD5的二进制值,装在一个 byte[16] 里面(16*8 = 128)
            //目标是把16进制串变成 1,2,3~A,B,C,D这种表达
            //一个byte是8bit,但是4bit就是一个16进制数字了,所以要把一个byte拆成2个16进制数字

            //算法是很笨的位运算算法
            int[] tempNum = new int[2];//tempNum[0]是高4位 的 16进制数字, tempNum[1]是低4位的16进制数字
            int[] tempArr = new int[8];//这是8位, int型的一个 0,1 序列,就是直接把 1个byte[]的0和1放进一个int[]数组, 其实是为了调试方便才这么去写
            for (int j = 0; j < 8; j++) {
                if (((array[i] >> j) & 1) == 1) {
                    tempArr[7 - j] = 1;
                    //为什么这里是 7 - j 从右向左填充?
                    //因为上面右移对1取模 ((array[i] >> j) & 1的操作只能取"最右侧的位"
                }
            }
            for (int j = 0; j < 8; j++) {
                //循环遍历"刚刚变成0,1序列的int数组"
                //对于2个初始值为0的数字tempNum[0]和tempNum[1],如果当前mark表明是高4位(即tempArr[0-3]),则填充tempNum[0],如果当前mark表明是低4位(即tempArr[4-7]),填充tempNum[1]
                int mark = j <= 3 ? 0 : 1;
                if (tempArr[j] == 1) {
                    tempNum[mark] = tempNum[mark] << 1;//左移一位
                    tempNum[mark] = tempNum[mark] | 1;//把最低位变成1
                } else {
                    tempNum[mark] = tempNum[mark] << 1;//左移一位,默认左移是补0
                }
            }
            for (int j = 0; j < 2; j++) {//把tempNum[0]和tempNum[1]转换为char字符
                if (0 <= tempNum[j] && tempNum[j] <= 9) {
                    str.append(tempNum[j]);
                } else {
                    switch (tempNum[j]) {
                        case 10:
                            str.append('A');
                            break;
                        case 11:
                            str.append('B');
                            break;
                        case 12:
                            str.append('C');
                            break;
                        case 13:
                            str.append('D');
                            break;
                        case 14:
                            str.append('E');
                            break;
                        case 15:
                            str.append('F');
                            break;
                        default:
                            return null;
                    }
                }
            }
        }

        return str.toString();
    }

    //参数: 一个有32个字符的 String
    static byte[] MD5ArrFromStringToHex(String str) {
        byte[] MD5_byte_arr = new byte[16];
        for (int i = 0; i < 32; i += 2) {
            byte temp_byte = 0;//这个是被"填充"的临时byte
            char[] c_arr = new char[]{str.charAt(i), str.charAt(i + 1)};//str.charAt(i)是byte的高4bit转变为的字符, str.charAt(i + 1)是byte的低4bit转变为的字符

            int[] temp_4bit = new int[2];//temp_4bit[0]是byte的高4bit, temp_4bit[1]是byte的低4bit
            for (int j = 0; j < 2; j++) {
                switch (c_arr[j]){
                    case '0':
                        temp_4bit[j] = 0;
                        break;
                    case '1':
                        temp_4bit[j] = 1;
                        break;
                    case '2':
                        temp_4bit[j] =2;
                        break;
                    case '3':
                        temp_4bit[j] =3;
                        break;
                    case '4':
                        temp_4bit[j] =4;
                        break;
                    case '5':
                        temp_4bit[j] =5;
                        break;
                    case '6':
                        temp_4bit[j] =6;
                        break;
                    case '7':
                        temp_4bit[j] =7;
                        break;
                    case '8':
                        temp_4bit[j] =8;
                        break;
                    case '9':
                        temp_4bit[j] =9;
                        break;
                    case 'A':
                        temp_4bit[j] =10;
                        break;
                    case 'B':
                        temp_4bit[j] =11;
                        break;
                    case 'C':
                        temp_4bit[j] =12;
                        break;
                    case 'D':
                        temp_4bit[j] =13;
                        break;
                    case 'E':
                        temp_4bit[j] =14;
                        break;
                    case 'F':
                        temp_4bit[j] =15;
                        break;
                }
                //这里的算法是
                temp_byte = (byte) ((byte)(temp_4bit[0] << 4) | temp_4bit[1]);
                MD5_byte_arr[i/2] = temp_byte;
            }
        }
        return MD5_byte_arr;
    }

    public static void main(String[] args) {
        long beginTime = System.currentTimeMillis();
        File file = new File("D:/STUDY/Computer_Network/Computer_Network_Labs/Lab3_P2P/studyJavaTCP/ClientDemo/target/classes/jdk api 1.8_google.CHM");
        //File file = new File("D:/STUDY/Computer_Network/Computer_Network_Labs/Lab3_P2P/studyJavaTCP/ClientDemo/target/classes/composite-big.CHM");
        String md5 = getMD5(file);
        long endTime = System.currentTimeMillis();
        System.out.println("MD5:" + md5 + "\n 耗时:" + ((endTime - beginTime) / 1000) + "s");

        byte[] arr = MD5ArrFromStringToHex("07FD69AD27BF2F633A20A2A4F3FC4807");
        String str = MD5ArrFromHexToString(arr);
        System.out.println(str);
    }
}
//07FD69AD27BF2F633A20A2A4F3FC4807